<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Tadpoles</title>
    <link rel="stylesheet" href="../css/style.css">
    <script type="text/javascript" src="../../dist/paper-full.js"></script>
    <script type="text/paperscript" canvas="canvas">
        // Adapted from Flocking Processing example by Daniel Schiffman:
        // http://processing.org/learning/topics/flocking.html

        var Boid = Base.extend({
            initialize: function(position, maxSpeed, maxForce) {
                var strength = Math.random() * 0.5;
                this.acceleration = new Point();
                this.vector = Point.random() * 2 - 1;
                this.position = position.clone();
                this.radius = 30;
                this.maxSpeed = maxSpeed + strength;
                this.maxForce = maxForce + strength;
                this.amount = strength * 10 + 10;
                this.count = 0;
                this.createItems();
            },

            run: function(boids) {
                this.lastLoc = this.position.clone();
                if (!groupTogether) {
                    this.flock(boids);
                } else {
                    this.align(boids);
                }
                this.borders();
                this.update();
                this.calculateTail();
                this.moveHead();
            },

            calculateTail: function() {
                var segments = this.path.segments,
                    shortSegments = this.shortPath.segments;
                var speed = this.vector.length;
                var pieceLength = 5 + speed / 3;
                var point = this.position;
                segments[0].point = shortSegments[0].point = point;
                // Chain goes the other way than the movement
                var lastVector = -this.vector;
                for (var i = 1; i < this.amount; i++) {
                    var vector = segments[i].point - point;
                    this.count += speed * 10;
                    var wave = Math.sin((this.count + i * 3) / 300);
                    var sway = lastVector.rotate(90).normalize(wave);
                    point += lastVector.normalize(pieceLength) + sway;
                    segments[i].point = point;
                    if (i < 3)
                        shortSegments[i].point = point;
                    lastVector = vector;
                }
                this.path.smooth();
            },

            createItems: function() {
                this.head = new Shape.Ellipse({
                    center: [0, 0],
                    size: [13, 8],
                    fillColor: 'white'
                });

                this.path = new Path({
                    strokeColor: 'white',
                    strokeWidth: 2,
                    strokeCap: 'round'
                });
                for (var i = 0; i < this.amount; i++)
                    this.path.add(new Point());

                this.shortPath = new Path({
                    strokeColor: 'white',
                    strokeWidth: 4,
                    strokeCap: 'round'
                });
                for (var i = 0; i < Math.min(3, this.amount); i++)
                    this.shortPath.add(new Point());
            },

            moveHead: function() {
                this.head.position = this.position;
                this.head.rotation = this.vector.angle;
            },

            // We accumulate a new acceleration each time based on three rules
            flock: function(boids) {
                var separation = this.separate(boids) * 3;
                var alignment = this.align(boids);
                var cohesion = this.cohesion(boids);
                this.acceleration += separation + alignment + cohesion;
            },

            update: function() {
                // Update velocity
                this.vector += this.acceleration;
                // Limit speed (vector#limit?)
                this.vector.length = Math.min(this.maxSpeed, this.vector.length);
                this.position += this.vector;
                // Reset acceleration to 0 each cycle
                this.acceleration = new Point();
            },

            seek: function(target) {
                this.acceleration += this.steer(target, false);
            },

            arrive: function(target) {
                this.acceleration += this.steer(target, true);
            },

            borders: function() {
                var vector = new Point();
                var position = this.position;
                var radius = this.radius;
                var size = view.size;
                if (position.x < -radius) vector.x = size.width + radius;
                if (position.y < -radius) vector.y = size.height + radius;
                if (position.x > size.width + radius) vector.x = -size.width -radius;
                if (position.y > size.height + radius) vector.y = -size.height -radius;
                if (!vector.isZero()) {
                    this.position += vector;
                    var segments = this.path.segments;
                    for (var i = 0; i < this.amount; i++) {
                        segments[i].point += vector;
                    }
                }
            },

            // A method that calculates a steering vector towards a target
            // Takes a second argument, if true, it slows down as it approaches
            // the target
            steer: function(target, slowdown) {
                var steer,
                    desired = target - this.position;
                var distance = desired.length;
                // Two options for desired vector magnitude
                // (1 -- based on distance, 2 -- maxSpeed)
                if (slowdown && distance < 100) {
                    // This damping is somewhat arbitrary:
                    desired.length = this.maxSpeed * (distance / 100);
                } else {
                    desired.length = this.maxSpeed;
                }
                steer = desired - this.vector;
                steer.length = Math.min(this.maxForce, steer.length);
                return steer;
            },

            separate: function(boids) {
                var desiredSeperation = 60;
                var steer = new Point();
                var count = 0;
                // For every boid in the system, check if it's too close
                for (var i = 0, l = boids.length; i < l; i++) {
                    var other = boids[i];
                    var vector = this.position - other.position;
                    var distance = vector.length;
                    if (distance > 0 && distance < desiredSeperation) {
                        // Calculate vector pointing away from neighbor
                        steer += vector.normalize(1 / distance);
                        count++;
                    }
                }
                // Average -- divide by how many
                if (count > 0)
                    steer /= count;
                if (!steer.isZero()) {
                    // Implement Reynolds: Steering = Desired - Velocity
                    steer.length = this.maxSpeed;
                    steer -= this.vector;
                    steer.length = Math.min(steer.length, this.maxForce);
                }
                return steer;
            },

            // Alignment
            // For every nearby boid in the system, calculate the average velocity
            align: function(boids) {
                var neighborDist = 25;
                var steer = new Point();
                var count = 0;
                for (var i = 0, l = boids.length; i < l; i++) {
                    var other = boids[i];
                    var distance = this.position.getDistance(other.position);
                    if (distance > 0 && distance < neighborDist) {
                        steer += other.vector;
                        count++;
                    }
                }

                if (count > 0)
                    steer /= count;
                if (!steer.isZero()) {
                    // Implement Reynolds: Steering = Desired - Velocity
                    steer.length = this.maxSpeed;
                    steer -= this.vector;
                    steer.length = Math.min(steer.length, this.maxForce);
                }
                return steer;
            },

            // Cohesion
            // For the average location (i.e. center) of all nearby boids,
            // calculate steering vector towards that location
            cohesion: function(boids) {
                var neighborDist = 100;
                var sum = new Point();
                var count = 0;
                for (var i = 0, l = boids.length; i < l; i++) {
                    var other = boids[i];
                    var distance = this.position.getDistance(other.position);
                    if (distance > 0 && distance < neighborDist) {
                        sum += other.position; // Add location
                        count++;
                    }
                }
                if (count > 0) {
                    sum /= count;
                    // Steer towards the location
                    return this.steer(sum, false);
                }
                return sum;
            }
        });

        var heartPath = new Path('M514.69629,624.70313c-7.10205,-27.02441 -17.2373,-52.39453 -30.40576,-76.10059c-13.17383,-23.70703 -38.65137,-60.52246 -76.44434,-110.45801c-27.71631,-36.64355 -44.78174,-59.89355 -51.19189,-69.74414c-10.5376,-16.02979 -18.15527,-30.74951 -22.84717,-44.14893c-4.69727,-13.39893 -7.04297,-26.97021 -7.04297,-40.71289c0,-25.42432 8.47119,-46.72559 25.42383,-63.90381c16.94775,-17.17871 37.90527,-25.76758 62.87354,-25.76758c25.19287,0 47.06885,8.93262 65.62158,26.79834c13.96826,13.28662 25.30615,33.10059 34.01318,59.4375c7.55859,-25.88037 18.20898,-45.57666 31.95215,-59.09424c19.00879,-18.32178 40.99707,-27.48535 65.96484,-27.48535c24.7373,0 45.69531,8.53564 62.87305,25.5957c17.17871,17.06592 25.76855,37.39551 25.76855,60.98389c0,20.61377 -5.04102,42.08691 -15.11719,64.41895c-10.08203,22.33203 -29.54687,51.59521 -58.40723,87.78271c-37.56738,47.41211 -64.93457,86.35352 -82.11328,116.8125c-13.51758,24.0498 -23.82422,49.24902 -30.9209,75.58594z');

        var boids = [];
        var groupTogether = false;

        // Add the boids:
        for (var i = 0; i < 30; i++) {
            var position = Point.random() * view.size;
            boids.push(new Boid(position, 10, 0.05));
        }


        function onFrame(event) {
            for (var i = 0, l = boids.length; i < l; i++) {
                if (groupTogether) {
                    var length = ((i + event.count / 30) % l) / l * heartPath.length;
                    var point = heartPath.getPointAt(length);
                    if (point)
                        boids[i].arrive(point);
                }
                boids[i].run(boids);
            }
        }

        // Reposition the heart path whenever the window is resized:
        function onResize(event) {
            heartPath.fitBounds(view.bounds);
            heartPath.scale(0.8);
        }

        function onMouseDown(event) {
            groupTogether = !groupTogether;
        }

        function onKeyDown(event) {
            if (event.key == 'space') {
                var layer = project.activeLayer;
                layer.selected = !layer.selected;
                return false;
            }
        }
    </script>
    <style>
        body {
            background: black;
        }
    </style>
</head>
<body>
    <canvas id="canvas" resize></canvas>
</body>
</html>
